STOSERVE - Structured Storage in an in-process server


SUMMARY
=======

The STOSERVE sample introduces the COPaper COM object, which models a
sheet of white drawing paper. COPaper objects expose a set of features for
free-form drawing on the paper surface using "ink" of specified color and
width. The functionality is outwardly similar to other "scribble" C++
tutorial samples. The difference in the STOSERVE/STOCLIEN samples is an
architecture based primarily on OLE technology. The electronic drawing
paper features of COPaper objects are available to clients through a
custom IPaper interface. COPaper implements the IPaper interface. A clear
architectural distinction is kept between client and server. No graphical
user interface (GUI) is provided by COPaper. The design of the COPaper
object relies on the client for all GUI behavior. COPaper encapsulates
only the server-based capture and storage of the drawn ink data.

The ink data that is drawn on the COPaper surface can be stored in and
loaded from OLE compound files. The IPaper Save and Load methods accept an
IStorage interface pointer. COPaper uses this client-provided IStorage
interface to store the drawing data.

The primary focus of this code sample is on the use of OLE structured
storage services as provided in the OLE compound files implementation.
STOSERVE works with the STOCLIEN code sample to illustrate the joint use
of compound file storage by client and server.

COPaper is housed in an in-process server and is made publicly available
as a custom COM component. Like all other servers in this tutorial series,
STOSERVE is a self-registering COM server. It makes the COPaper object
type available to clients as the DllPaper component using a
CLSID_DllPaper registration in the Registry.

As was the case in the previous CONSERVE server, connectable object
features are supported in COPaper. The IConnectionPointContainer interface
is exposed, and an appropriate connection point is implemented. In this
context, an outgoing custom IPaperSink interface is declared for use in
sending notifications to the client.

The two IPaper and IPaperSink custom interfaces are declared in IPAPER.H
located in the common sibling \INC directory. The GUIDs for the interfaces
and objects are defined in PAPGUIDS.H located in that same common include
directory.

The CThreaded facility in APPUTIL is used by STOSERVE to achieve thread
safety, as it was in the FRESERVE sample. COPaper objects are derived from
the CThreaded class and inherit its OwnThis and UnOwnThis methods. These
methods allow only one thread at a time to have access to the STOSERVE
server and to COPaper objects managed by the server.

For functional descriptions and a tutorial code tour of STOSERVE, see the
Code Tour section below. See also STOCLIEN.TXT in the sibling STOCLIEN
directory for more details on the STOCLIEN client application and how it
works with STOSERVE.DLL. You must build STOSERVE.DLL before building or
running STOCLIEN.

STOSERVE's makefile automatically registers STOSERVE's DllPaper COM
component in the registry. This component must be registered before
STOSERVE is available to outside COM clients as a server for that
component. This self-registration is done using the REGISTER.EXE utility
built in the REGISTER sample. To build or run STOSERVE, you should build
the REGISTER code sample first.

For details on setting up your system to build and test the code samples
in this OLE Tutorial series, see TUTORIAL.TXT. The supplied MAKEFILE is
Microsoft NMAKE-compatible. To create a debug build, issue the NMAKE
command in the Command Prompt window.

Usage
-----

STOSERVE is a DLL that is intended primarily as a COM server. Although it
can be implicitly loaded by linking to its associated .LIB file, it is
normally used after an explicit LoadLibrary call, usually from within the
COM function CoGetClassObject. STOSERVE is a self-registering in-process
server. The makefile that builds this sample automatically registers this
server in the registry. You can manually initiate its self-registration by
issuing the following command at the command prompt:

  nmake register

You can also directly invoke the REGISTER.EXE command at the command prompt
while in the STOSERVE directory.

  ..\register\register.exe stoserve.dll

These registration commands require a prior build of the REGISTER sample
in this series, as well as a prior build of STOSERVE.DLL.

To use STOSERVE, a client program does not need to include STOSERVE.H or
link to STOSERVE.LIB. A COM client of STOSERVE obtains access solely
through its object's CLSID and OLE services. For STOSERVE, that CLSID is
CLSID_DllPaper (defined in file PAPGUIDS.H in the \INC sibling directory).
The STOCLIEN code sample shows how the client obtains this access.


CODE TOUR
=========

Files         Description

STOSERVE.TXT  This file.
MAKEFILE      The generic makefile for building the STOSERVE.DLL
              code sample of this lesson.
STOSERVE.H    The include file for declaring as imported or defining as
              exported the service functions in STOSERVE.DLL.
STOSERVE.CPP  The main implementation file for STOSERVE.DLL. Has DllMain
              and the COM server functions (for example, DllGetClassObject).
STOSERVE.RC   The DLL resource definition file for the executable.
STOSERVE.ICO  The icon resource for the executable.
SERVER.H      The include file for the server control C++ object.
SERVER.CPP    The implementation file for the server control C++ object.
FACTORY.H     The include file for the server's class factory COM objects.
FACTORY.CPP   The implementation file for the server's class factories.
CONNECT.H     The include file for the connection point enumerator,
              connection point, and connection enumerator classes.
CONNECT.CPP   The implementation file for the connection point enumerator,
              connection point, and connection enumerators objects.
PAPER.H       The include file for the COPaper COM object class.
PAPER.CPP     The implementation file for the COPaper COM object class
              and the connection points.

STOSERVE uses many of the utility classes and services provided by
APPUTIL. For more details on APPUTIL, study the APPUTIL library's source
code and APPUTIL.TXT, located in the sibling \APPUTIL directory.

This sample is part of a graduated series of tutorial code samples. This
tour assumes that you have some exposure to those previous samples. It
does not revisit earlier topics of basic interface implementation
techniques, COM object construction, in-process server construction, class
factory construction, and connectable object construction. For information
on these topics, study the earlier tutorial samples.

The major topics covered in this code tour are:

  An overview of how STOSERVE is designed to work with connected clients
  The IPaper interface
  COPaper's management of paper drawing data
  COPaper's use of structured storage in compound files to save and
    load paper data
  COPaper's use of the IPaperSink interface for client notifications
  Unicode considerations

The COPaper COM object is the single object type managed by this STOSERVE
in-process server. COPaper is constructed as a connectable COM object with
an implementation of the standard IConnectionPointContainer interface and
an implementation of the custom IPaper interface. COPaper exposes the
IPaper interface so clients can perform a small set of electronic paper
operations on an instance of COPaper. The essential operations are
starting an ink drawing sequence, drawing the ink data on COPaper's
virtual paper surface, and ending the ink drawing sequence. In this
scheme, the client is assumed to be a GUI application driven by a mouse or
tablet device. The client is responsible for translating the mouse
movements into requests to COPaper, which saves these requests as ink
data.

There are two levels of ink data saving in COPaper. COPaper saves the ink
data in a RAM-based array that represents the current drawing, and COPaper
persistently saves an entire drawing into a compound file. Methods in the
IPaper interface perform both.

Since the client does not manage the drawn paper data but is responsible
for rendering it as an image on the screen, the IPaper implementation in
COPaper must expose a method that allows the client to obtain the drawing
data. The connectable object technology in COPaper is used for this
purpose. A CONNPOINT_PAPERSINK connection point is implemented by COPaper
so clients can connect to COPaper to recieve the ink data for drawing. The
client first calls the IPaper::Redraw method on the COPaper object to
request all the ink data of the current drawing. COPaper's implementation
of Redraw then uses the client's implementation of IPaperSink to pass the
data to the client.

As the user interactively draws in the client, it paints the data
immediately to the screen while also sending it to COPaper for saving.
When the client needs the screen to be repainted, it calls COPaper's
Redraw method. Such repainting is common in applications. For example,
repainting occurs when the client's window is overlaid by another
application's window. The client has a bitmap rendering of the drawn
image, but the bitmap is easily lost and must often be repainted. The
client relies on COPaper in the server for the ink data needed for
repainting.

This is a common client/server division of labor. It is especially
appropriate when there is a need for multiple clients to share the data.
COPaper is coded to allow for this eventuality. It uses APPUTIL's
CThreaded facility to achieve thread safety. An application that might
exploit this design is a shared whiteboard application, where multiple
clients can contribute to a commonly viewed drawing. OLE's support for
Distributed COM (DCOM) supports this kind of workgroup application usage
across the network. See the earlier REMCLIEN sample for more details on
DCOM.

A more modest use of COPaper's client/server scheme is to integrate
behavior for objects implemented in different applications on the same
machine. In this case, the clients might be ActiveX containers in separate
applications that share data managed by the same object. This data may not
be the ink drawing data that the DllPaper component supports. STOSERVE can
be used as a starting framework for programming components that manage
other kinds of shared data.

STOSERVE provides COPaper objects, which are controlled primarily by their
native IPaper interface. Here is a summary of IPaper's methods from
IPAPER.H in the sibling \INC directory.

  InitPaper
    Initializes paper object and create ink data array.
  Lock
    Gives client control of the paper and locks out other clients.
  Unlock
    Relinquishes client control of the paper.
  Load
    Loads paper content from client's compound file and notifies sinks.
  Save
    Saves paper content to client's compound file.
  InkStart
    Starts color ink drawing to the paper surface.
  InkDraw
    Puts ink data points on the electronic paper surface.
  InkStop
    Stops ink drawing to the paper surface.
  Erase
    Erase the current paper content and notifies sinks.
  Resize
    Resizes the drawing paper rectangle size and notifies sinks.
  Redraw
    Redraws contents of paper object and notifies sinks.

Of interest for this code sample on compound files are the Load and Save
methods. We will cover these in detail below, but first we will look at
the ink data that will be loaded from and saved to compound files.

InkStart, InkDraw, and InkStop are the methods used by clients to command
COPaper to record ink drawing sequences. The client will typically respond
to a WM_LBUTTONDOWN message as the start of an ink drawing sequence by
calling InkStart on COPaper. As the user moves the mouse or pen to draw
while holding down the left button, the client will respond to repeated
WM_MOUSEMOVE messages with corresponding calls to InkDraw. When the user
releases the left mouse button, the client will respond to a WM_LBUTTONUP
message with a call to InkStop, which marks the end of the ink drawing
sequence.

InkStart tells COPaper the start position for the drawing sequence in
client window coordinates. It also passes the currently selected ink color
and width. The client maintains these selections; COPaper merely records
them when the InkStart call is made. InkDraw is called repeatedly to tell
COPaper the succession of window coordinates that represent the drawing
motion of the mouse or pen. InkStop tells COPaper to mark the end of a
drawing sequence.

COPaper packages the pen color, width, and coordinates into INKDATA
structures and stores them in a dynamically allocated array that it
manages in memory. Here are the declarations for the INKDATA structure
from PAPER.H.

  // The types of Ink Data.
  #define INKTYPE_NONE  0
  #define INKTYPE_START 1
  #define INKTYPE_DRAW  2
  #define INKTYPE_STOP  3

  // The Ink Data structure.
  typedef struct _INKDATA
  {
    SHORT nType;            // Ink Type.
    SHORT nX;               // X-coordinate of ink point.
    SHORT nY;               // Y-coordinate of ink point.
    SHORT nWidth;           // Ink line width in pixels.
    COLORREF crColor;       // Ink color.
  } INKDATA;

The dynamic array of these INKDATA packets is pointed to by m_paInkData, a
member of the IPaper implementation class. The array is created within the
IPaper::InitPaper method with an initial allocation. For details, see the
InitPaper method and the private NextSlot utility method of the CImpIPaper
implementation in PAPER.H. The InkStart, InkDraw, and InkStop methods use
NextSlot to obtain new slots in the array. The array is expanded
dynamically by NextSlot as the need arises.

The client calls the IPaper::Erase method to erase the current drawing.
This method does not reallocate the array; it simply marks all current ink
data as INKTYPE_NONE and resets the array's end-of-data index to zero.

The client calls the IPaper::Lock and Unlock methods to manage ownership
of COPaper for drawing. These methods are provided to organize access
among multiple clients to the drawing held in a shared COPaper.

The client calls the IPaper::Resize method to tell COPaper that the user
resized the current drawing paper rectangle. This coordinate data is kept
in a PAPER_PROPERTIES structure, which is stored with the ink data when
all the paper data is stored in a compound file. Here is the
PAPER_PROPERTIES declaration from PAPER.H.

  #define PAPER_TITLE_SIZE 64
  typedef struct _PAPER_PROPERTIES
  {
    LONG lInkDataVersion;
    LONG lInkArraySize;
    COLORREF crWinColor;
    RECT WinRect;
    WCHAR wszTitle[PAPER_TITLE_SIZE];
    WCHAR wszAuthor[PAPER_TITLE_SIZE];
    WCHAR wszReserved1[PAPER_TITLE_SIZE];
    WCHAR wszReserved2[PAPER_TITLE_SIZE];
  } PAPER_PROPERTIES;

The PAPER_PROPERTIES structure is designed so that new ink data formats
can be added at any time as the DllPaper component evolves. The IPaper
interface is general enough that a subsequent version of the DllPaper
component may store a different ink data format while implementing the
same IPaper interface. Because the methods of IPaper do not depend on a
specific ink data format, a new version of the DllPaper component that
does support a different ink data format can use this same interface.

The paper properties stored in a compound file record the current size of
the ink data array. The proper array size can then be allocated to
accommodate the ink data read from the file.

The PAPER_PROPERTIES structure also stores the paper surface's drawing
rectangle size and background window color.

Though not used in the STOSERVE/STOCLIEN samples, a drawing title and
an author name can also be stored.

Creation date and last modified dates are not included in these paper
properties, because the IStorage interface used to access compound files
manages this information.

The principal focus in this code sample is how COPaper can load and save
its paper data in compound files. We now look at the IPaper Load and Save
method implementations in detail.

Here is IPaper::Save from PAPER.CPP.

  STDMETHODIMP COPaper::CImpIPaper::Save(
                 SHORT nLockKey,
                 IStorage* pIStorage)
  {
    HRESULT hr = E_FAIL;
    IStream* pIStream;
    ULONG ulToWrite, ulWritten;

    if (OwnThis())
    {
      if (m_bLocked && m_cLockKey == nLockKey && NULL != pIStorage)
      {
        // First use OLE service to mark this compound file as one that is
        // handled by our server component, DllPaper.
        WriteClassStg(pIStorage, CLSID_DllPaper);

        // Use OLE Service to write user-readable clipboard format into
        // the compound file.
        WriteFmtUserTypeStg(pIStorage, m_ClipBdFmt, TEXT(CLIPBDFMT_STR));

        // Create the stream to be used for the actual paper data.
        // Call it "PAPERDATA".
        hr = pIStorage->CreateStream(
               STREAM_PAPERDATA_USTR,
               STGM_CREATE | STGM_WRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
               0,
               0,
               &pIStream);
        if (SUCCEEDED(hr))
        {
          // Got a stream. Now write data into it.
          // First write PAPER_PROPERTIES structure.
          m_PaperProperties.lInkArraySize = m_lInkDataEnd+1;
          m_PaperProperties.crWinColor = m_crWinColor;
          m_PaperProperties.WinRect.right = m_WinRect.right;
          m_PaperProperties.WinRect.bottom = m_WinRect.bottom;
          ulToWrite = sizeof(PAPER_PROPERTIES);
          hr = pIStream->Write(&m_PaperProperties, ulToWrite, &ulWritten);
          if (SUCCEEDED(hr) && ulToWrite != ulWritten)
            hr = STG_E_CANTSAVE;
          if (SUCCEEDED(hr))
          {
            // Now write the complete array of Ink Data.
            ulToWrite = m_PaperProperties.lInkArraySize * sizeof(INKDATA);
            hr = pIStream->Write(m_paInkData, ulToWrite, &ulWritten);
            if (SUCCEEDED(hr) && ulToWrite != ulWritten)
              hr = STG_E_CANTSAVE;
          }

          // We are done with the stream so release it.
          pIStream->Release();
        }
      }

      UnOwnThis();
    }

    // Notify all other connected clients that Paper is now saved.
    if (SUCCEEDED(hr))
      m_pBackObj->NotifySinks(PAPER_EVENT_SAVED, 0, 0, 0, 0);

    return hr;
  }

In the division of labor between client and server, COPaper does not
create the compound file that is used to store paper data. For both the
Save and Load methods, the client passes an IStorage interface pointer for
an existing compound file. It then uses the IStorage to write and read
data in that compound file. In IPaper::Save above, several types of data
are stored.

The CLSID for DllPaper, CLSID_DllPaper, is serialized and stored in a
special OLE-controlled stream within the storage object called
"\001CompObj". The WriteClassStg OLE service function performs this
storage. This stored CLSID data can be used to associate the storage's
content with the DllPaper component that created and can interpret it. In
this sample, the root storage is passed by STOCLIEN, and thus the entire
compound file is associated with the DllPaper component. This CLSID data
can be retrieved later with a call to the ReadClassStg OLE service
function.

Since DllPaper deals with editable data, it is also appropriate to record
a clipboard format in the storage. The WriteFmtUserTypeString OLE service
function is called to store both a clipboard format designation and a
user-readable name for the format. The user-readable name is meant for GUI
display in selection lists. The name passed above uses a macro,
CLIPBDFMT_STR, which is defined as "DllPaper 1.0" in PAPER.H. This stored
clipboard data can be retrieved later with a call to the OLE service
function ReadFmtUserTypeStg. This function returns a string value that is
allocated using the task memory allocator. The caller is responsible for
freeing the string.

Save next creates a stream in the storage for the COPaper paper data. The
stream is called "PAPERDATA" and is passed using the STREAM_PAPERDATA_USTR
macro. The IStorage::CreateStream method requires that this string be in
Unicode. Since the string is fixed at compile time, the macro is defined
as Unicode in PAPER.H.

  #define STREAM_PAPERDATA_USTR L"PAPERDATA"

The 'L' before the string, indicating LONG, achieves this.

The CreateStream method creates and opens a stream within the specified
storage. An IStream interface pointer for the new stream is passed in a
caller's interface pointer variable. AddRef is called on this interface
pointer within CreateStream, and the caller must release this pointer
after using it. The CreateStream function is passed numerous access mode
flags, as follows.

  STGM_CREATE | STGM_WRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE

STGM_CREATE creates a new stream or overwrites an existing one of the same
name. STGM_WRITE opens the stream with write permission. STGM_DIRECT opens
the stream for direct access, as opposed to transacted access.
STGM_SHARE_EXCLUSIVE opens the file for exclusive, non-shared use by the
caller.

After the PAPERDATA stream is successfully created, the IStream interface
is used to write into the stream. The IStream::Write method is used to
first store the content of the PAPER_PROPERTIES structure. This is
essentially a properties header at the front of the stream. Because the
version number is the first thing in the file, it can be read
independently to determine how to deal with the data that follows. If the
amount of data actually written does not equal the amount requested, the
Save method is aborted, and it returns STG_E_CANTSAVE.

Saving the entire array of ink data into the stream is simple.

  // Now write the complete array of Ink Data.
  ulToWrite = m_PaperProperties.lInkArraySize * sizeof(INKDATA);
  hr = pIStream->Write(m_paInkData, ulToWrite, &ulWritten);
  if (SUCCEEDED(hr) && ulToWrite != ulWritten)
    hr = STG_E_CANTSAVE;

Since the IStream::Write method operates on a byte array, the number of
bytes of stored ink data in the array is calculated and the write
operation begins at the start of the array. If the amount of data actually
written does not equal the amount requested, the Save method returns
STG_E_CANTSAVE.

After the stream is written, the IPaper::Save method releases the IStream
pointer it was using.

The Save method also calls the client's IPaperSink (in COPaper's internal
NotifySinks method) to notify the client that the save operation is
complete. At this point the Save method returns to the calling client,
which will typically release the IStorage pointer.

Here is the Save method's opposite, IPaper::Load from PAPER.CPP.

  STDMETHODIMP COPaper::CImpIPaper::Load(
                 SHORT nLockKey,
                 IStorage* pIStorage)
  {
    HRESULT hr = E_FAIL;
    IStream* pIStream;
    INKDATA* paInkData;
    ULONG ulToRead, ulReadIn;
    LONG lNewArraySize;
    PAPER_PROPERTIES NewProps;

    if (OwnThis())
    {
      if (m_bLocked && m_cLockKey == nLockKey && NULL != pIStorage)
      {
        // Open the "PAPERDATA" stream where the paper data is stored.
        hr = pIStorage->OpenStream(
               STREAM_PAPERDATA_USTR,
               0,
               STGM_READ | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
               0,
               &pIStream);
        if (SUCCEEDED(hr))
        {
          // We have the paper data stream. First read the Paper Properties.
          ulToRead = sizeof(PAPER_PROPERTIES);
          hr = pIStream->Read(
                           &NewProps,
                           ulToRead,
                           &ulReadIn);
          if (SUCCEEDED(hr) && ulToRead != ulReadIn)
            hr = E_FAIL;
          if (SUCCEEDED(hr))
          {
            // Deal with the different versions of ink data format.
            switch (NewProps.lInkDataVersion)
            {
              case INKDATA_VERSION10:
                // Allocate an ink data array big enough--add some
                // extra too.
                lNewArraySize = NewProps.lInkArraySize + INKDATA_ALLOC;
                paInkData = new INKDATA[(LONG) lNewArraySize];
                if (NULL != paInkData)
                {
                  // Delete the entire old ink data array.
                  delete [] m_paInkData;

                  // Assign the new array.
                  m_paInkData = paInkData;
                  m_lInkDataMax = lNewArraySize;

                  // Now read the complete array of Ink Data.
                  ulToRead = NewProps.lInkArraySize * sizeof(INKDATA);
                  hr = pIStream->Read(m_paInkData, ulToRead, &ulReadIn);
                  if (SUCCEEDED(hr) && ulToRead != ulReadIn)
                    hr = E_FAIL;
                  if (SUCCEEDED(hr))
                  {
                    // Rig COPaper to use the new PAPER_PROPERTIES info.
                    m_lInkDataEnd = NewProps.lInkArraySize-1;
                    m_crWinColor = NewProps.crWinColor;
                    m_WinRect.right = NewProps.WinRect.right;
                    m_WinRect.bottom = NewProps.WinRect.bottom;

                    // Copy the new properties into current properties.
                    memcpy(
                      &m_PaperProperties,
                      &NewProps,
                      sizeof(PAPER_PROPERTIES));
                  }
                }
                else
                  hr = E_OUTOFMEMORY;
                break;
              default:
                hr = E_FAIL;  // Bad version.
                break;
            }
          }

          // We are done with the stream so release it.
          pIStream->Release();
        }
      }

      UnOwnThis();
    }

    // Notify all other connected clients that Paper is now loaded.
    // If we didn't load then erase to a safe, empty ink data array.
    if (SUCCEEDED(hr))
      m_pBackObj->NotifySinks(PAPER_EVENT_LOADED, 0, 0, 0, 0);
    else
      Erase(nLockKey);

    return hr;
  }

This time the IStorage::OpenStream method is called to open the existing
stream in the storage called "PAPERDATA". Access mode flags are for read
only, direct, and non-shared exclusive access. Once the stream is open,
the IStream::Read method is called to read the PAPER_PROPERTIES structure.
If the amount actually read does not equal the amount requested, the load
operation is aborted, and E_FAIL is returned. If the format version in the
newly read PAPER_PROPERTIES is not recognized, then the load operation is
aborted and Load returns E_FAIL.

With a valid ink data format version, the size of the new ink data array
from the PAPER_PROPERTIES that was read in is used to allocate a new ink
data array of the required size. The existing ink data is deleted, and its
data is lost.  If this data was valuable, it should have been saved before
Load was called. After the new array is allocated, IStream::Read is called
again to read the data into the array from the stream. If this call
succeeds, the values in the newly read paper properties are adopted as the
current values for COPaper.

During this load operation, a temporary PAPER_PROPERTIES structure,
NewProps, was used to hold the new properties read in. If all succeeds
with the load, NewProps is copied into the PAPER_PROPERTIES structure,
m_PaperProperties. As before, after Load is done and the IStream is no
longer needed, the IStream pointer is released.

If there is an error at the end of Load, the ink data array is erased,
because it may contain damaged data.

If there is no error at the end of Load, the client's IPaperSink::Loaded
method is called (in COPaper's internal NotifySinks method) to notify the
client that the load operation is comleted. This is an important
notification for the client, because it needs to display this newly loaded
ink data. This notification makes significant use of COPaper's connectable
object features.

COPaper exposes the IConnectionPointContainer interface so clients can
connect to COPaper in order to receive notifications of specified events
that occur in COPaper. By exposing this interface, COPaper becomes a
connectable object. A client can call QueryInterface for this interface
and use it to obtain the object's connection points. The client
participation in this scheme is covered in the associated STOCLIEN sample.

Basically, the client implements what is called a sink in the form of a
sink object with a sink interface. The sink interface receives outgoing
event notification calls from COPaper after the sink is properly connected
by the client to a COPaper instance. The client makes the connection by
using a connection point object that is managed by COPaper. There can be
numerous connection points on a single connectable COM object. In the
STOSERVE sample, COPaper has only one connection point, which handles
drawing paper events.

Any number of clients can connect to a single connection point. The
CONNPOINT_PAPERSINK connection point in COPaper maintains a group of
connections that can grow dynamically at run time. The full
details on COPaper's connectable object support is coded in files
CONNECT.H and CONNECT.CPP and will not be covered here. The construction
is very similar to what was studied in the CONSERVE code sample.

The STOCLIEN client implements appropriate sink objects for the connection
points it expects to find in COPaper. From the context of COPaper, the
important sink object that STOCLIEN implements exposes the IPaperSink
interface. This is the outgoing interface used by COPaper to notify
STOCLIEN of various events in COPaper. Here is a summary of the methods in
IPaperSink from IPAPER.H in the \INC sibling directory.

  Locked
    A client has taken control of and locked the paper.
  Unlocked
    A client has relinquished control of the paper.
  Loaded
    A client has loaded paper content from its own compound file.
  Saved
    A client has saved paper content to its own compound file.
  InkStart
    A client started a color ink drawing sequence to the paper.
  InkDraw
    A client is putting ink data points on the paper surface.
  InkStop
    A client has stopped its ink drawing sequence to the paper.
  Erased
    A client has erased all ink data from the paper.
  Resized
    A client has resized the paper.

These methods are largely self-explanatory. Although the sink must
implement all these methods in some fashion, many are implemented as stubs
and are not used in the STOSERVE/STOCLIEN samples.

An important method used in these samples is the Loaded method. When
COPaper is told by the client to load a new drawing from file, it needs a
way to notify the client when the new data is loaded. To do this, COPaper
Calls IPaperSink::Loaded on the client sink. The client can then call
IPaper::Redraw to request that COPaper play back the new data to the
client. Redraw causes the loaded drawing in the client's display to be
repainted. When the load is completed, the newly loaded drawing is
displayed automatically in the window provided by the client.

The IPaper::Redraw method relies on the connection point technology.
Here is Redraw from PAPER.CPP.

  STDMETHODIMP COPaper::CImpIPaper::Redraw(
                 SHORT nLockKey)
  {
    HRESULT hr = E_FAIL;
    IConnectionPoint* pIConnectionPoint;
    IEnumConnections* pIEnum;
    CONNECTDATA ConnData;
    IPaperSink* pIPaperSink;
    SHORT nInkType;
    LONG i;

    if (OwnThis())
    {
      if (m_bLocked && m_cLockKey == nLockKey)
      {
        // Broadcast InkData notifications to all Sinks connected to
        // each connection point.

        // Here is the section for the PaperSink connection point--this is
        // currently the only connection point offered by COPaper objects.
        pIConnectionPoint =
          m_pBackObj->m_aConnectionPoints[CONNPOINT_PAPERSINK];
        if (NULL != pIConnectionPoint)
        {
          pIConnectionPoint->AddRef();
          hr = pIConnectionPoint->EnumConnections(&pIEnum);
          if (SUCCEEDED(hr))
          {
            // Loop thru the connection point's connections and if the
            // listening connection supports IPaperSink (ie, PaperSink
            // events) then send all the current Paper's Ink Data to it.
            while (NOERROR == pIEnum->Next(1, &ConnData, NULL))
            {
              hr = ConnData.pUnk->QueryInterface(
                                    IID_IPaperSink,
                                    (PPVOID)&pIPaperSink);
              if (SUCCEEDED(hr))
              {
                // Loop thru all the Ink Data and send it to this connected
                // client sink.
                for (i=0; i<m_lInkDataEnd+1; i++)
                {
                  nInkType = m_paInkData[i].nType;
                  switch (nInkType)
                  {
                    case INKTYPE_START:
                      pIPaperSink->InkStart(
                                     m_paInkData[i].nX,
                                     m_paInkData[i].nY,
                                     m_paInkData[i].nWidth,
                                     m_paInkData[i].crColor);
                      break;
                    case INKTYPE_DRAW:
                      pIPaperSink->InkDraw(
                                     m_paInkData[i].nX,
                                     m_paInkData[i].nY);
                      break;
                    case INKTYPE_STOP:
                      pIPaperSink->InkStop(
                                     m_paInkData[i].nX,
                                     m_paInkData[i].nY);
                      break;
                    default:
                      break;
                  }
                }
                pIPaperSink->Release();
              }
              ConnData.pUnk->Release();
            }
            pIEnum->Release();
          }
          pIConnectionPoint->Release();
        }
      }

      UnOwnThis();
    }

    return hr;
  }

The IPaperSink methods InkStart, InkDraw, and InkStop are used to send
individual ink data packets back to the client. On reception, the client
paints them in its display in the same manner as when it originally sent
them to COPaper. The above logic is general enough to allow for multiple
clients that are all listening on the same CONNPOINT_PAPERSINK connection
point. If a common COPaper instance was shared by these clients, they
could all display the same drawing by connecting to this connection point.

The WriteFmtUserTypeStg OLE service function used in the Save method shown
above requires Unicode string parameters. This is the case with OLE
service calls that take string parameters. When compiling for ANSI
strings, the expected Unicode parameters need conversion from ANSI to
Unicode. This process is achieved using some macros in APPUTIL.H that may
obscure the conversions that are performed. Take WriteFmtUserTypeStg as an
example. The following call appears in the Save method.

  WriteFmtUserTypeStg(pIStorage, m_ClipBdFmt, TEXT(CLIPBDFMT_STR));

When STOSERVE is compiled for ANSI (not for Unicode), this call actually
reduces to a call to an internal APPUTIL surrogate function. To support
this, the following macro scheme is used in APPUTIL.H, which is an
included file in all code sample .CPP files.

  #if !defined(UNICODE)

  STDAPI A_WriteFmtUserTypeStg(IStorage*, CLIPFORMAT, LPSTR);

  #if !defined(_NOANSIMACROS_)

  #undef WriteFmtUserTypeStg
  #define WriteFmtUserTypeStg(a, b, c) A_WriteFmtUserTypeStg(a, b, c)

  #endif

  #endif

The scheme uses surrogate service call functions. The ANSI versions of the
calls begin with A_. These surrogate ANSI functions are implemented in
APPUTIL.CPP. They are used when the code sample is being compiled for ANSI
strings (the default in the makefiles).  The OLE service functions that
the surrogates stand in place of support only Unicode string parameters.
This forces some string conversions from ANSI to Unicode before the real
OLE service call is made inside the surrogate.

For example, if UNICODE is not defined during compiling, any calls in the
samples to the WriteFmtUserTypeStg OLE service function are actually
changed by the macros into calls to an A_WriteFmtUserTypeStg function that
is implemented in APPUTIL.CPP. This function accepts the input ANSI string
pointer, converts it to a Unicode copy, and passes that Unicode copy as
the string parameter in a call to the actual WriteFmtUserTypeStg function.

Here is A_WriteFmtUserTypeStg from APPUTIL.CPP.

  STDAPI A_WriteFmtUserTypeStg(
           IStorage* pIStorage,
           CLIPFORMAT ClipFmt,
           LPSTR pszUserType)
  {
    HRESULT hr = E_INVALIDARG;
    WCHAR szUc[MAX_PATH];

    if (NULL != pszUserType)
    {
      // Convert from Ansi to Unicode in szUc.
      MultiByteToWideChar(CP_ACP, 0, pszUserType, -1, szUc, MAX_PATH);

      // Use the Unicode string in the actual OLE service call.
      hr = WriteFmtUserTypeStg(pIStorage, ClipFmt, szUc);
    }

    return hr;
  }
